import json
import re
from urllib.parse import urlparse

from django.conf import settings
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin

ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"
ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"
ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"
ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"
ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"
ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"

DEFAULT_CSRF_TOKEN_KEY = "verify"
DEFAULT_SESSION_TOKEN_KEY = "s-token"

default_headers = (
    "accept",
    "accept-encoding",
    "authorization",
    "content-type",
    "dnt",
    "origin",
    "user-agent",
    "x-csrftoken",
    "x-requested-with",
)

default_methods = ("DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT")


class CorsMiddleware(MiddlewareMixin):
    """
    为前后端分离项目设置跨域允许
    """

    def process_request(self, request):
        """
        因为前后段分离所以无法传递cookies 但是为了复用CSRF模块 需要主动设置参数
        """
        header_csrf_token_key = str(getattr(settings, "CSRF_TOKEN_KEY", DEFAULT_CSRF_TOKEN_KEY)).upper()
        cookie_token = request.META.get(f"HTTP_{header_csrf_token_key}".replace("-", "_"))
        if cookie_token:
            request.COOKIES[settings.CSRF_COOKIE_NAME] = cookie_token

        header_session_key = str(getattr(settings, "SESSION_TOKEN_KEY", DEFAULT_SESSION_TOKEN_KEY)).upper()
        session_key = request.META.get(f"HTTP_{header_session_key}".replace("-", "_"))
        if session_key:
            request.COOKIES[settings.SESSION_COOKIE_NAME] = session_key

        # django解析器默认不解析json格式数据，需要单独定义解析器解析
        content_type = request.META.get('CONTENT_TYPE', '')
        if not content_type:
            content_type = request.META.get('HTTP_CONTENT_TYPE', '')
        if content_type == 'application/json' and request.method == "POST":
            request.POST = json.loads(request.body)

    def process_response(self, request, response):
        """
        添加相应的 CORS 标头

        CORS_URLS_REGEX: str 匹配哪些url需要验证cors，默认全部验证
        CORS_ALLOW_CREDENTIALS: bool 是否可以接受cookies申请，默认False
        CORS_ORIGIN_ALLOW_ALL: bool 信任所有域，默认False
        CORS_ORIGIN_WHITELIST: tuple 信任域的网址字符串白名单
        CORS_ORIGIN_REGEX_WHITELIST: tuple 信任域的网址正则表达式白名单
        CORS_EXPOSE_HEADERS: tuple
        CORS_ALLOW_HEADERS: tuple
        CORS_ALLOW_METHODS: tuple
        """
        # 匹配哪些url需要验证cors，默认全部验证
        cors_urls_regex = getattr(settings, "CORS_URLS_REGEX", r"^.*$")
        enabled = bool(re.match(cors_urls_regex, request.path_info))

        if not enabled:
            return response

        patch_vary_headers(response, ["Origin"])

        origin = request.META.get("HTTP_ORIGIN")
        if not origin:
            return response

        # 将url分解为scheme协议、netloc服务器地址、path相对路径、params参数、query查询的条件等
        url = urlparse(origin)

        # 是否可以接受cookies申请
        cors_allow_credentials = getattr(settings, "CORS_ALLOW_CREDENTIALS", False)
        if cors_allow_credentials:
            response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = "true"

        # 允许接受所有域
        cors_origin_allow_all = getattr(settings, "CORS_ORIGIN_ALLOW_ALL", False)
        if not cors_origin_allow_all:
            # 信任域的网址字符串白名单
            cors_origin_whitelist = getattr(settings, "CORS_ORIGIN_WHITELIST", ())
            # origin是"null"并且在白名单内
            # 之后使用urlparse格式的时候null只有path="null"其余信息为空的问题，所以单独提取出来进行处理
            if not (origin == "null" and origin in cors_origin_whitelist):
                # 验证当前访问url是否在origin的网址字符串白名单中
                for origin in cors_origin_whitelist:
                    origin_parse_result = urlparse(origin)
                    if origin_parse_result.scheme == url.scheme and origin_parse_result.netloc == url.netloc:
                        break
                # 如果url不在origin的网址字符串白名单中
                else:
                    # 信任域的网址正则白名单
                    cors_origin_regex_whitelist = getattr(settings, "CORS_ORIGIN_REGEX_WHITELIST", ())
                    for domain_pattern in cors_origin_regex_whitelist:
                        if re.match(domain_pattern, origin):
                            break
                    # 如果url也不匹配origin的网址正则白名单中
                    else:
                        return response

        if cors_origin_allow_all and not cors_allow_credentials:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
        else:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin

        cors_expose_headers = getattr(settings, "CORS_EXPOSE_HEADERS", ())
        if cors_expose_headers:
            response[ACCESS_CONTROL_EXPOSE_HEADERS] = ", ".join(cors_expose_headers)

        if request.method == "OPTIONS":
            # 因为需要处理csrf以及cookies等，需要将新增的头信息设置为允许
            allow_headers = [str(getattr(settings, "CSRF_TOKEN_KEY", DEFAULT_CSRF_TOKEN_KEY)).lower(),
                             str(getattr(settings, "SESSION_TOKEN_KEY", DEFAULT_SESSION_TOKEN_KEY)).upper()]
            allow_headers.extend(getattr(settings, "CORS_ALLOW_HEADERS", default_headers))
            response[ACCESS_CONTROL_ALLOW_HEADERS] = ", ".join(allow_headers)
            response[ACCESS_CONTROL_ALLOW_METHODS] = ", ".join(getattr(settings, "CORS_ALLOW_METHODS", default_methods))
            cors_preflight_max_age = getattr(settings, "CORS_PREFLIGHT_MAX_AGE", 86400)
            if cors_preflight_max_age:
                response[ACCESS_CONTROL_MAX_AGE] = str(cors_preflight_max_age)

        header_session_key = str(getattr(settings, "SESSION_TOKEN_KEY", DEFAULT_SESSION_TOKEN_KEY)).upper()
        session_obj = response.cookies.get(settings.SESSION_COOKIE_NAME)
        if session_obj:
            response[header_session_key] = session_obj.value

        return response
